package cn.agrael.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Set;

import com.opensymphony.xwork2.inject.util.ReferenceMap;
import com.opensymphony.xwork2.inject.util.ReferenceType;

/**
 * 对象工具。
 * <p>
 * 如果没有特别说明，向该类的方法传递 null 都会抛出 {@link NullPointerException}
 * 
 * @author <a href="mailto:agraellee@gmail.com">Agrael·Lee</a>
 * 
 */
public class ObjectUtils {

	/** 以 Class 为 key , Field[] 为 value 的每个类的所有 Field 的缓存 */
	private static final ReferenceMap<Class<?>, Field[]> FIELD_CACHE_MAP = new ReferenceMap<Class<?>, Field[]>(ReferenceType.WEAK,
			ReferenceType.SOFT);

	/** setter方法前缀 */
	public static final String SETTER = "set";

	/** getter方法前缀 */
	public static final String GETTER = "get";

	private ObjectUtils() {
	}

	/**
	 * 得到当前线程中默认的类加载器。
	 * 
	 * @return 当前线程中默认的类加载器。
	 */
	public static ClassLoader getDefaultClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	/**
	 * 根据类的唯一限定名以及合适的类加载器创建{@link Class}对象。
	 * 
	 * @param className className 类的唯一限定名。
	 * @param classLoader 类加载器。
	 * @return 合适的{@link Class}对象。
	 * @throws TypeNotPresentException 如果没找到限定名的类则抛出该异常。
	 */
	public static Class<?> applicationClass(String className, ClassLoader classLoader) {
		if (classLoader == null) {
			classLoader = getDefaultClassLoader();
			if (classLoader == null) {
				classLoader = ObjectUtils.class.getClassLoader();
			}
		}
		try {
			return (classLoader.loadClass(className));
		} catch (ClassNotFoundException e) {
			throw new TypeNotPresentException(className, e);
		}
	}

	/**
	 * 根据类的唯一限定名创建{@link Class}对象。
	 * 
	 * @param className 类的唯一限定名。
	 * @return 合适的{@link Class}对象。
	 * @throws ClassNotFoundException 如果没找到限定名的类则抛出该异常。
	 */
	public static Class<?> applicationClass(String className) {
		return applicationClass(className, null);
	}

	/**
	 * 创建一个对象实例。。
	 * 
	 * @param <T> 实例对象的类型
	 * @param className 要创建对象的类的完全限定名。
	 * @return 对应的实例。
	 * @throws TypeNotPresentException 如果没有找到 className 对应的类。
	 * @throws GeneratorException 如果创建实例发生其他错误时。
	 */
	@SuppressWarnings("unchecked")
	public static <T> T createObject(String className) {
		Class<?> clazz = null;
		clazz = applicationClass(className);
		return (T) createObject(clazz);
	}

	/**
	 * 创建一个对象实例。
	 * 
	 * @param <T> 实例对象的类型
	 * @param clazz 要创建对象的类的{@link Class}对象。
	 * @return 对应的实例。
	 * @throws GeneratorException 如果创建实例发生错误时。
	 */
	public static <T> T createObject(Class<T> clazz) {
		try {
			Constructor<T> constructor = clazz.getConstructor();
			constructorSetAccessible(constructor);
			return constructor.newInstance();
		} catch (Exception e) {
			throw new GeneratorException(e);
		}
	}

	/**
	 * 返回 Field 对象的一个数组，这些对象反映 object 的 Class
	 * 对象所表示的类或接口所声明的所有字段，包括公共、保护、默认（包）访问和私有字段，但不包括继承的字段
	 * 。返回数组中的元素没有排序，也没有任何特定的顺序。如果该类或接口不声明任何字段，或者此 Class 对象表示一个基本类型、一个数组类或
	 * void，则此方法返回一个长度为 0 的数组。
	 * 
	 * @param object 需要得到 Field 数组的 实例。
	 * @return 表示 object 的 Class 对象所有已声明字段的 Field 对象的数组 。
	 */
	public static Field[] getAllField(Object object) {
		return getAllField(object.getClass());
	}

	/**
	 * 返回 Field 对象的一个数组，这些对象反映 clazz
	 * 对象所表示的类或接口所声明的所有字段，包括公共、保护、默认（包）访问和私有字段，但不包括继承的字段
	 * 。返回数组中的元素没有排序，也没有任何特定的顺序。如果该类或接口不声明任何字段，或者此 Class 对象表示一个基本类型、一个数组类或
	 * void，则此方法返回一个长度为 0 的数组。
	 * 
	 * @param clazz 需要得到 Field 数组的 Class。
	 * @return 表示 clazz 对象所有已声明字段的 Field 对象的数组 。
	 */
	public static Field[] getAllField(Class<?> clazz) {
		Field[] fields = FIELD_CACHE_MAP.get(clazz);
		if (fields == null) {
			synchronized (FIELD_CACHE_MAP) {
				if ((fields = FIELD_CACHE_MAP.get(clazz)) == null) {
					Set<Field> fieldSet = new HashSet<Field>();
					allField(clazz, fieldSet);
					fields = fieldSet.toArray(new Field[fieldSet.size()]);
					FIELD_CACHE_MAP.put(clazz, fields);
				}
			}
		}
		return fields;
	}

	private static void allField(Class<?> clazz, Set<Field> fields) {
		if (clazz == null) {
			return;
		}
		// 添加自己的 Field
		for (Field field : clazz.getDeclaredFields()) {
			fields.add(field);
		}
		// 解析父类的 Field
		allField(clazz.getSuperclass(), fields);
	}

	/**
	 * 将此method的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的method在使用时应该取消 Java
	 * 语言访问检查。
	 * <p>
	 * 如果存在安全管理器,则在启用特权的情况下执行 {@link Method#setAccessible(boolean)}。
	 * 
	 * @param method 要将 accessible 设置为 true的 Method。
	 * 
	 * @see AccessController#doPrivileged(PrivilegedAction)
	 */
	public static void methodSetAccessible(final Method method) {
		if (!method.isAccessible()) {
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged(new PrivilegedAction<Object>() {
					public Object run() {
						method.setAccessible(true);
						return null;
					}
				});
			} else {
				method.setAccessible(true);
			}
		}
	}

	/**
	 * 将此field的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的field在使用时应该取消 Java 语言访问检查。
	 * <p>
	 * 如果存在安全管理器,则在启用特权的情况下执行 {@link Field#setAccessible(boolean)}。
	 * 
	 * @param field 要将 accessible 设置为 true的 Field。
	 * 
	 * @see AccessController#doPrivileged(PrivilegedAction)
	 */
	public static void fieldSetAccessible(final Field field) {
		if (!field.isAccessible()) {
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged(new PrivilegedAction<Object>() {
					public Object run() {
						field.setAccessible(true);
						return null;
					}
				});
			} else {
				field.setAccessible(true);
			}
		}
	}

	/**
	 * 将此constructor的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的constructor在使用时应该取消
	 * Java 语言访问检查。
	 * <p>
	 * 如果存在安全管理器,则在启用特权的情况下执行 {@link Constructor#setAccessible(boolean)}。
	 * 
	 * @param constructor 要将 accessible 设置为 true的 Constructor。
	 * 
	 * @see AccessController#doPrivileged(PrivilegedAction)
	 */
	public static void constructorSetAccessible(final Constructor<?> constructor) {
		if (!constructor.isAccessible()) {
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged(new PrivilegedAction<Object>() {
					public Object run() {
						constructor.setAccessible(true);
						return null;
					}
				});
			} else {
				constructor.setAccessible(true);
			}
		}
	}
}